﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;

namespace Microsoft.HomeServer.HomeServerConsoleTab.DuplicationInfo
{
    internal class DriveExtenderReparsePointData
    {
        public static readonly Guid DRIVE_EXTENDER_REPARSE_GUID = new Guid("70616b34-803d-4fcc-9d1e-ebe4507af574");

        internal class Volume
        {
            private Guid guid;
            private UInt32 unknown0;
            private UInt32 unknown1;
            private UInt16 unknown2;

            public Volume(Guid guid, UInt32 unknown0, UInt32 unknown1, UInt16 unknown2)
            {
                this.guid = guid;
                this.unknown0 = unknown0;
                this.unknown1 = unknown1;
                this.unknown2 = unknown2;
            }

            public Guid Guid { get { return this.guid; } }
            public UInt32 Unknown0 { get { return this.unknown0; } }
            public UInt32 Unknown1 { get { return this.unknown1; } }
            public UInt16 Unknown2 { get { return this.unknown2; } }

            public override string ToString()
            {
                return string.Format(
                    "({0}, {1:x8}, {2:x8}, {3:x4})",
                    this.guid,
                    this.unknown0,
                    this.unknown1,
                    this.unknown2
                    );
            }
        }

        private UInt32 version;
        private IList<Guid> guids;
        private IList<Volume> volumes;

        public DriveExtenderReparsePointData(ReparsePoint.REPARSE_GUID_DATA_BUFFER buf)
        {
            if (buf.ReparseGuid == DRIVE_EXTENDER_REPARSE_GUID)
            {
                using (
                    BinaryReader reader =
                        new BinaryReader(
                            new MemoryStream(buf.GenericReparseBuffer, 0, buf.ReparseDataLength),
                            Encoding.Unicode
                            )
                    )
                {
                    this.version = reader.ReadUInt32();
                    if (version != 0x0101)
                    {
                        throw new ArgumentException(string.Format("Unsupported version. ({0})", version));
                    }

                    int volumeCount = reader.ReadInt32();
                    if (buf.ReparseDataLength != 8 + volumeCount * 82)
                    {
                        throw new ArgumentException(
                            string.Format(
                                "Unexpected ReparseDataLength. (expected:{0}, actual:{1})",
                                8 + volumeCount * 82,
                                buf.ReparseDataLength
                                )
                            );
                    }

                    this.guids = new List<Guid>(volumeCount);
                    this.volumes = new List<Volume>(volumeCount);
                    for (int i = 0; i < volumeCount; i++)
                    {
                        Guid guid = new Guid(new string(reader.ReadChars(36)));
                        UInt32 unknown0 = reader.ReadUInt32();
                        UInt32 unknown1 = reader.ReadUInt32();
                        UInt16 unknown2 = reader.ReadUInt16();
                        volumes.Add(new Volume(guid, unknown0, unknown1, unknown2));
                        guids.Add(guid);
                    }
                }
            }
            else
            {
                throw new ArgumentException("GUID does not match.");
            }
        }

        public IList<Guid> Guids { get { return this.guids; } }
        public IList<Volume> Volumes { get { return this.volumes; } }
    }

    internal class ReparsePoint
    {
        private const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = (16 * 1024);

        [StructLayout(LayoutKind.Sequential)]
        internal struct REPARSE_GUID_DATA_BUFFER
        {
            public UInt32 ReparseTag;
            public UInt16 ReparseDataLength;
            public UInt16 Reserved;
            public Guid ReparseGuid;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAXIMUM_REPARSE_DATA_BUFFER_SIZE)]
            public byte[] GenericReparseBuffer;
        }

        private const UInt32 FILE_SHARE_READ = 0x00000001;
        private const UInt32 FILE_SHARE_WRITE = 0x00000002;
        private const UInt32 FILE_SHARE_DELETE = 0x00000004;
        private const Int32 OPEN_EXISTING = 3;
        private const UInt32 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
        private const UInt32 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
        private const Int32 INVALID_HANDLE_VALUE = -1;

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern IntPtr CreateFile(
            string lpFileName,
            UInt32 dwDesiredAccess,
            UInt32 dwShareMode,
            IntPtr lpSecurityAttributes,
            UInt32 dwCreationDisposition,
            UInt32 dwFlagsAndAttributes,
            IntPtr hTemplateFile
            );

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern Int32 CloseHandle(
            IntPtr hObject
            );

        private const Int32 FSCTL_GET_REPARSE_POINT = 0x900a8;

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern Int32 DeviceIoControl(
            IntPtr hDevice,
            UInt32 dwIoControlCode,
            IntPtr lpInBuffer,
            UInt32 nInBufferSize,
            IntPtr lpOutBuffer,
            UInt32 nOutBufferSize,
            out UInt32 lpBytesReturned,
            IntPtr lpOverlapped
            );

        public static REPARSE_GUID_DATA_BUFFER GetReparsePointData(string path)
        {
            IntPtr handle = new IntPtr(INVALID_HANDLE_VALUE);
            IntPtr outBuf = IntPtr.Zero;
            try
            {
                handle = CreateFile(
                    path,
                    0,
                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                    IntPtr.Zero,
                    OPEN_EXISTING,
                    FILE_FLAG_OPEN_REPARSE_POINT,
                    IntPtr.Zero
                    );
                if (handle.ToInt32() == INVALID_HANDLE_VALUE)
                {
                    throw new IOException("CreateFile() failed.");
                }

                int outBufSize = Marshal.SizeOf(typeof(REPARSE_GUID_DATA_BUFFER));
                outBuf = Marshal.AllocHGlobal(outBufSize);

                UInt32 bytesReturned;
                Int32 ret = DeviceIoControl(
                    handle,
                    FSCTL_GET_REPARSE_POINT,
                    IntPtr.Zero,
                    0,
                    outBuf,
                    (UInt32)outBufSize,
                    out bytesReturned,
                    IntPtr.Zero
                    );
                if (ret != 0)
                {
                    REPARSE_GUID_DATA_BUFFER buf =
                        (REPARSE_GUID_DATA_BUFFER)Marshal.PtrToStructure(outBuf, typeof(REPARSE_GUID_DATA_BUFFER));
                    return buf;
                }
                else
                {
                    throw new IOException("DeviceIoControl() failed.");
                }
            }
            finally
            {
                if (handle.ToInt32() != INVALID_HANDLE_VALUE)
                {
                    CloseHandle(handle);
                }
                if (outBuf != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(outBuf);
                }
            }
        }
    }
}

